package com.project.common.data.mybatis.interceptor;

import com.project.common.pagination.Pagination;
import com.project.common.pagination.PaginationContext;
import com.project.common.pagination.PaginationHandler;
import com.project.common.pagination.PaginationHandlerFactory;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.Field;
import java.sql.Connection;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

@SuppressWarnings("rawtypes")
@Intercepts({@Signature(
        type = StatementHandler.class,
        method = "prepare",
        args = {Connection.class, Integer.class})
})
public class PaginationInterceptor implements Interceptor {
    private static final Logger LOGGER = LoggerFactory.getLogger(PaginationInterceptor.class);
    private static final String META_OBJECT_NAME = "delegate.mappedStatement";
    private static final String PAGINATION_SQL_OBJECT_NAME = "delegate.boundSql.sql";
    private String dialect;

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        StatementHandler handler = (StatementHandler) invocation.getTarget();
        MetaObject metaObject = SystemMetaObject.forObject(handler);
        MappedStatement mappedStatement =
                (MappedStatement) metaObject.getValue(META_OBJECT_NAME);
        String simpleLimitKeyWord = "limit";
        String simpleLimitKeyWordUpper = "LIMIT";
        String dialectMysql = "mysql";
        if (this.dialect.equals(dialectMysql)) {
            if (!mappedStatement.getSqlCommandType().equals(SqlCommandType.SELECT)) {
                return invocation.proceed();
            }
        }

        Pagination pagination = PaginationContext.currentContext().getPagination();
        if (pagination == null) {
            LOGGER.debug(handler.getBoundSql().getSql());
            return invocation.proceed();
        }
        //处理sql增加查询limit限制
        String sql = handler.getBoundSql().getSql();
        if (this.dialect.equals(dialectMysql)) {
            sql = sql.replaceAll(simpleLimitKeyWordUpper, simpleLimitKeyWord);
            if (!sql.contains(simpleLimitKeyWord)) {
                sql = sql + " " + simpleLimitKeyWord + " " + pagination.to();
            }
        }
        // 通过反射修改sql语句
        Field field = handler.getBoundSql().getClass().getDeclaredField("sql");
        field.setAccessible(true);
        field.set(handler.getBoundSql(), sql);

        PaginationHandlerFactory paginationHandlerFactory =
                PaginationHandlerFactory.getDefault();
        PaginationHandler paginationHandler =
                paginationHandlerFactory.getPaginationHandler(this.dialect);
        String paginationSql = paginationHandler.paginationStatement(
                mappedStatement,
                handler.getBoundSql(),
                pagination);
        metaObject.setValue(PAGINATION_SQL_OBJECT_NAME, paginationSql);
        LOGGER.debug(paginationSql);
        return invocation.proceed();
    }

    private String getLastLimitRemovedSql(String sql, String keyWord) {
        return sql.substring(0, sql.lastIndexOf(keyWord)) + sql.substring(sql.indexOf(")", sql.lastIndexOf(keyWord)));
    }

    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {
        this.dialect = properties.getProperty("dialect");
        LOGGER.info("dialect:" + this.dialect);
    }

    private List<String> getPatternList(String sql, String regex) {
        List<String> list = new ArrayList<>();
        Pattern pattern = Pattern.compile(regex);
        Matcher matcher = pattern.matcher(sql);
        while (matcher.find()) {
            list.add(matcher.group());
        }
        return list;
    }
}
